package com.wjy.ioc;

import com.wjy.ioc.exception.BeanGeneratorException;
import com.wjy.ioc.exception.IocException;
import com.wjy.ioc.exception.NoSuchBeanException;

import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 单例Bean创建生命周期管理
 *
 * @author wjy
 * @date 2020/9/11
 */
public class SingletonBeanGeneratorImpl extends BaseBeanGeneratorImpl {

    private final BeanContainer beanContainer = new BeanContainer();

    public SingletonBeanGeneratorImpl() {
    }

    @Override
    public Object createBean(BeanDefinition definition) {
        if (!definition.isSingleton()) {
            throw new BeanGeneratorException("使用 SingletonBeanGeneratorImpl 生产非 Singleton Bean");
        }
        // 先从容器中获取
        Object bean = this.beanContainer.getBean(definition.getBeanName());
        if (Objects.nonNull(bean)) {
            return bean;
        }
        // 如果没有则新建
        bean = super.createBean(definition);
        // 将实例化的Bean放入IOC容器
        this.beanContainer.setBean(definition.getBeanName(), bean);
        return bean;
    }

    @Override
    protected Object injectionProperties(Object bean, BeanDefinition definition) {
        final Class<?> beanClass = definition.getBeanClass();
        for (String attributeName : definition.attributeNames()) {
            Object attribute = definition.getAttribute(attributeName);

            // 属性是IOC管理Bean
            if (attribute instanceof BeanDefinition) {
                attribute = this.createBean((BeanDefinition) attribute);
            }

            try {
                final Field field = beanClass.getDeclaredField(attributeName);
                field.setAccessible(true);
                field.set(bean, attribute);
            } catch (IllegalAccessException | NoSuchFieldException e) {
                throw new IocException("属性注入失败", e);
            }
        }
        return bean;
    }

    @Override
    protected Object postBeforeProcess(Object bean, BeanDefinition definition) {
        final Object obj = super.postBeforeProcess(bean, definition);
        // 暂存Bean
        this.beanContainer.cacheBean(definition.getBeanName(), () -> postAfterProcess(bean, definition));
        return obj;
    }

    @Override
    protected Object postAfterProcess(Object bean, BeanDefinition definition) {
        // 确保每个Bean的后置处理只执行一次
        if (this.beanContainer.isPreProcessed(definition.getBeanName())) {
            bean = this.beanContainer.getPreProcessedBean(definition.getBeanName());
        } else {
            bean = super.postAfterProcess(bean, definition);
        }
        return bean;
    }

    /**
     * 获取IOC单例容器
     *
     * @return IOC容器
     */
    public SingletonBeanFactoryImpl getSingletonObjects() {
        return this.beanContainer.getSingletonObjects();
    }


    /**
     * 存放实例化的单例Bean，并处理循环依赖问题
     */
    class BeanContainer {

        private final SingletonBeanFactoryImpl singletonObjects = new SingletonBeanFactoryImpl();

        private final Map<String, Object> earlySingletonObjects = new HashMap<>();

        private final Map<String, FactoryBean> singletonObjectFactory = new HashMap<>();

        private final Set<String> preProcessingBeanNameSet = new HashSet<>();

        public Object getBean(@Nonnull String beanName) {
            Object bean = null;
            try {
                bean = this.singletonObjects.getBean(beanName);
            } catch (NoSuchBeanException e) {
                logger.debug("SingletonBeanGenerator get bean from SingletonBeanFactory failed:{}", e.getMessage());
            }
            if (Objects.isNull(bean)) {
                // 如果多个线程同时访问，只允许一个线程，进入并创建，另一个线程等待创建完成后再进入，不然会出现重复创建问题
                synchronized (this.earlySingletonObjects) {
                    bean = this.earlySingletonObjects.get(beanName);
                    if (Objects.isNull(bean)) {
                        final FactoryBean factoryBean = this.singletonObjectFactory.get(beanName);
                        if (Objects.nonNull(factoryBean)) {
                            bean = factoryBean.getObject();
                            this.earlySingletonObjects.put(beanName, bean);
                            this.singletonObjectFactory.remove(beanName);
                            preProcessingBeanNameSet.add(beanName);
                        }
                    }
                }
            }
            return bean;
        }

        public void setBean(@Nonnull String beanName, @Nonnull Object bean) {
            this.singletonObjects.setBean(beanName, bean);
            this.earlySingletonObjects.remove(beanName);
            this.singletonObjectFactory.remove(beanName);
        }

        public void cacheBean(@Nonnull String beanName, @Nonnull FactoryBean factoryBean) {
            this.singletonObjectFactory.put(beanName, factoryBean);
        }

        public boolean isPreProcessed(@Nonnull String beanName) {
            return preProcessingBeanNameSet.contains(beanName);
        }

        public Object getPreProcessedBean(@Nonnull String beanName) {
            final Object bean = this.earlySingletonObjects.get(beanName);
            preProcessingBeanNameSet.remove(beanName);
            return bean;
        }

        public SingletonBeanFactoryImpl getSingletonObjects() {
            return singletonObjects;
        }
    }
}
